home *** CD-ROM | disk | FTP | other *** search
/ Visual Cafe 3 / Visual Cafe 3.ISO / Vcafe / Main.bin / SimpleDateFormat.java < prev    next >
Text File  |  1998-09-22  |  54KB  |  1,280 lines

  1. /*
  2.  * @(#)SimpleDateFormat.java    1.31 98/08/27
  3.  *
  4.  * (C) Copyright Taligent, Inc. 1996 - All Rights Reserved
  5.  * (C) Copyright IBM Corp. 1996 - All Rights Reserved
  6.  *
  7.  * Portions copyright (c) 1996 Sun Microsystems, Inc. All Rights Reserved.
  8.  *
  9.  *   The original version of this source code and documentation is copyrighted
  10.  * and owned by Taligent, Inc., a wholly-owned subsidiary of IBM. These
  11.  * materials are provided under terms of a License Agreement between Taligent
  12.  * and Sun. This technology is protected by multiple US and International
  13.  * patents. This notice and attribution to Taligent may not be removed.
  14.  *   Taligent is a registered trademark of Taligent, Inc.
  15.  *
  16.  * Permission to use, copy, modify, and distribute this software
  17.  * and its documentation for NON-COMMERCIAL purposes and without
  18.  * fee is hereby granted provided that this copyright notice
  19.  * appears in all copies. Please refer to the file "copyright.html"
  20.  * for further important copyright and licensing information.
  21.  *
  22.  * SUN MAKES NO REPRESENTATIONS OR WARRANTIES ABOUT THE SUITABILITY OF
  23.  * THE SOFTWARE, EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED
  24.  * TO THE IMPLIED WARRANTIES OF MERCHANTABILITY, FITNESS FOR A
  25.  * PARTICULAR PURPOSE, OR NON-INFRINGEMENT. SUN SHALL NOT BE LIABLE FOR
  26.  * ANY DAMAGES SUFFERED BY LICENSEE AS A RESULT OF USING, MODIFYING OR
  27.  * DISTRIBUTING THIS SOFTWARE OR ITS DERIVATIVES.
  28.  *
  29.  */
  30.  
  31. package java.text;
  32. import java.util.TimeZone;
  33. import java.util.Calendar;
  34. import java.util.Date;
  35. import java.util.Locale;
  36. import java.util.ResourceBundle;
  37. import java.util.SimpleTimeZone;
  38. import java.util.GregorianCalendar;
  39. import java.io.ObjectInputStream;
  40. import java.io.IOException;
  41. import java.lang.ClassNotFoundException;
  42. import java.util.Hashtable;
  43.  
  44. /**
  45.  * <code>SimpleDateFormat</code> is a concrete class for formatting and
  46.  * parsing dates in a locale-sensitive manner. It allows for formatting
  47.  * (date -> text), parsing (text -> date), and normalization.
  48.  *
  49.  * <p>
  50.  * <code>SimpleDateFormat</code> allows you to start by choosing
  51.  * any user-defined patterns for date-time formatting. However, you
  52.  * are encouraged to create a date-time formatter with either
  53.  * <code>getTimeInstance</code>, <code>getDateInstance</code>, or
  54.  * <code>getDateTimeInstance</code> in <code>DateFormat</code>. Each
  55.  * of these class methods can return a date/time formatter initialized
  56.  * with a default format pattern. You may modify the format pattern
  57.  * using the <code>applyPattern</code> methods as desired.
  58.  * For more information on using these methods, see
  59.  * <a href="java.text.DateFormat.html"><code>DateFormat</code></a>.
  60.  *
  61.  * <p>
  62.  * <strong>Time Format Syntax:</strong>
  63.  * <p>
  64.  * To specify the time format use a <em>time pattern</em> string.
  65.  * In this pattern, all ASCII letters are reserved as pattern letters,
  66.  * which are defined as the following:
  67.  * <blockquote>
  68.  * <pre>
  69.  * Symbol   Meaning                 Presentation        Example
  70.  * ------   -------                 ------------        -------
  71.  * G        era designator          (Text)              AD
  72.  * y        year                    (Number)            1996
  73.  * M        month in year           (Text & Number)     July & 07
  74.  * d        day in month            (Number)            10
  75.  * h        hour in am/pm (1~12)    (Number)            12
  76.  * H        hour in day (0~23)      (Number)            0
  77.  * m        minute in hour          (Number)            30
  78.  * s        second in minute        (Number)            55
  79.  * S        millisecond             (Number)            978
  80.  * E        day in week             (Text)              Tuesday
  81.  * D        day in year             (Number)            189
  82.  * F        day of week in month    (Number)            2 (2nd Wed in July)
  83.  * w        week in year            (Number)            27
  84.  * W        week in month           (Number)            2
  85.  * a        am/pm marker            (Text)              PM
  86.  * k        hour in day (1~24)      (Number)            24
  87.  * K        hour in am/pm (0~11)    (Number)            0
  88.  * z        time zone               (Text)              Pacific Standard Time
  89.  * '        escape for text         (Delimiter)
  90.  * ''       single quote            (Literal)           '
  91.  * </pre>
  92.  * </blockquote>
  93.  * The count of pattern letters determine the format.
  94.  * <p>
  95.  * <strong>(Text)</strong>: 4 or more pattern letters--use full form,
  96.  * < 4--use short or abbreviated form if one exists.
  97.  * <p>
  98.  * <strong>(Number)</strong>: the minimum number of digits. Shorter
  99.  * numbers are zero-padded to this amount. Year is handled specially;
  100.  * that is, if the count of 'y' is 2, the Year will be truncated to 2 digits.
  101.  * <p>
  102.  * <strong>(Text & Number)</strong>: 3 or over, use text, otherwise use number.
  103.  * <p>
  104.  * Any characters in the pattern that are not in the ranges of ['a'..'z']
  105.  * and ['A'..'Z'] will be treated as quoted text. For instance, characters
  106.  * like ':', '.', ' ', '#' and '@' will appear in the resulting time text
  107.  * even they are not embraced within single quotes.
  108.  * <p>
  109.  * A pattern containing any invalid pattern letter will result in a thrown
  110.  * exception during formatting or parsing.
  111.  *
  112.  * <p>
  113.  * <strong>Examples Using the US Locale:</strong>
  114.  * <blockquote>
  115.  * <pre>
  116.  * Format Pattern                         Result
  117.  * --------------                         -------
  118.  * "yyyy.MM.dd G 'at' hh:mm:ss z"    ->>  1996.07.10 AD at 15:08:56 PDT
  119.  * "EEE, MMM d, ''yy"                ->>  Wed, July 10, '96
  120.  * "h:mm a"                          ->>  12:08 PM
  121.  * "hh 'o''clock' a, zzzz"           ->>  12 o'clock PM, Pacific Daylight Time
  122.  * "K:mm a, z"                       ->>  0:00 PM, PST
  123.  * "yyyyy.MMMMM.dd GGG hh:mm aaa"    ->>  1996.July.10 AD 12:08 PM
  124.  * </pre>
  125.  * </blockquote>
  126.  * <strong>Code Sample:</strong>
  127.  * <pre>
  128.  * <blockquote>
  129.  * SimpleTimeZone pdt = new SimpleTimeZone(-8 * 60 * 60 * 1000, "PST");
  130.  * pdt.setStartRule(DateFields.APRIL, 1, DateFields.SUNDAY, 2*60*60*1000);
  131.  * pdt.setEndRule(DateFields.OCTOBER, -1, DateFields.SUNDAY, 2*60*60*1000);
  132.  *
  133.  * // Format the current time.
  134.  * SimpleDateFormat formatter
  135.  *     = new SimpleDateFormat ("yyyy.mm.dd e 'at' hh:mm:ss a zzz");
  136.  * Date currentTime_1 = new Date();
  137.  * String dateString = formatter.format(currentTime_1);
  138.  *
  139.  * // Parse the previous string back into a Date.
  140.  * ParsePosition pos = new ParsePosition(0);
  141.  * Date currentTime_2 = formatter.parse(dateString, pos);
  142.  * </pre>
  143.  * </blockquote>
  144.  * In the example, the time value <code>currentTime_2</code> obtained from
  145.  * parsing will be equal to <code>currentTime_1</code>. However, they may not be
  146.  * equal if the am/pm marker 'a' is left out from the format pattern while
  147.  * the "hour in am/pm" pattern symbol is used. This information loss can
  148.  * happen when formatting the time in PM.
  149.  *
  150.  * <p>
  151.  * When parsing a date string using the abbreviated year pattern,
  152.  * SimpleDateFormat must interpret the abbreviated year
  153.  * relative to some century.  It does this by adjusting dates to be
  154.  * within 80 years before and 20 years after the time the SimpleDateFormat
  155.  * instance is created. For example, using a pattern of MM/dd/yy and a
  156.  * SimpleDateFormat instance created on Jan 1, 1997,  the string
  157.  * "01/11/12" would be interpreted as Jan 11, 2012 while the string "05/04/64"
  158.  * would be interpreted as May 4, 1964.
  159.  *
  160.  * <p>
  161.  * For time zones that have no names, use strings GMT+hours:minutes or
  162.  * GMT-hours:minutes.
  163.  *
  164.  * <p>
  165.  * The calendar defines what is the first day of the week, the first week
  166.  * of the year, whether hours are zero based or not (0 vs 12 or 24), and the
  167.  * time zone. There is one common decimal format to handle all the numbers;
  168.  * the digit count is handled programmatically according to the pattern.
  169.  *
  170.  * @see          java.util.Calendar
  171.  * @see          java.util.GregorianCalendar
  172.  * @see          java.util.TimeZone
  173.  * @see          DateFormat
  174.  * @see          DateFormatSymbols
  175.  * @see          DecimalFormat
  176.  * @version      1.31 08/27/98
  177.  * @author       Mark Davis, Chen-Lieh Huang, Alan Liu
  178.  */
  179. public class SimpleDateFormat extends DateFormat {
  180.  
  181.     // the official serial version ID which says cryptically
  182.     // which version we're compatible with
  183.     static final long serialVersionUID = 4774881970558875024L;
  184.  
  185.     // the internal serial version which says which version was written
  186.     // - 0 (default) for version up to JDK 1.1.3
  187.     // - 1 for version from JDK 1.1.4, which includes a new field
  188.     static final int currentSerialVersion = 1;
  189.     private int serialVersionOnStream = currentSerialVersion;
  190.  
  191.     private String pattern;
  192.     private DateFormatSymbols formatData;
  193.  
  194.     // if dates have ambiguous years, we map them into the century starting
  195.     // at defaultCenturyStart, which may be any date.
  196.     private Date defaultCenturyStart; // field new in JDK 1.1.4
  197.     transient private int defaultCenturyStartYear;
  198.  
  199.     private static final int millisPerHour = 60 * 60 * 1000;
  200.     private static final int millisPerMinute = 60 * 1000;
  201.  
  202.     // For time zones that have no names, use strings GMT+minutes and
  203.     // GMT-minutes. For instance, in France the time zone is GMT+60.
  204.     private static final String GMT_PLUS = "GMT+";
  205.     private static final String GMT_MINUS = "GMT-";
  206.     private static final String GMT = "GMT";
  207.  
  208.     /**
  209.      * Cache to hold the DateTimePatterns of a Locale.
  210.      */
  211.     private static final Hashtable cachedLocaleData = new Hashtable(3);
  212.  
  213.  
  214.     /**
  215.      * Construct a SimpleDateFormat using the default pattern for the default
  216.      * locale.  <b>Note:</b> Not all locales support SimpleDateFormat; for full
  217.      * generality, use the factory methods in the DateFormat class.
  218.      *
  219.      * @see java.text.DateFormat
  220.      */
  221.     public SimpleDateFormat() {
  222.         this(SHORT, SHORT + 4, Locale.getDefault());
  223.     }
  224.  
  225.     /**
  226.      * Construct a SimpleDateFormat using the given pattern in the default
  227.      * locale.  <b>Note:</b> Not all locales support SimpleDateFormat; for full
  228.      * generality, use the factory methods in the DateFormat class.
  229.      */
  230.     public SimpleDateFormat(String pattern)
  231.     {
  232.         this(pattern, Locale.getDefault());
  233.     }
  234.  
  235.     /**
  236.      * Construct a SimpleDateFormat using the given pattern and locale.
  237.      * <b>Note:</b> Not all locales support SimpleDateFormat; for full
  238.      * generality, use the factory methods in the DateFormat class.
  239.      */
  240.     public SimpleDateFormat(String pattern, Locale loc)
  241.     {
  242.         this.pattern = pattern;
  243.         this.formatData = new DateFormatSymbols(loc);
  244.         initialize(loc);
  245.     }
  246.  
  247.     /**
  248.      * Construct a SimpleDateFormat using the given pattern and
  249.      * locale-specific symbol data.
  250.      */
  251.     public SimpleDateFormat(String pattern, DateFormatSymbols formatData)
  252.     {
  253.         this.pattern = pattern;
  254.         this.formatData = (DateFormatSymbols) formatData.clone();
  255.         initialize(Locale.getDefault());
  256.     }
  257.  
  258.     /* Package-private, called by DateFormat factory methods */
  259.     SimpleDateFormat(int timeStyle, int dateStyle, Locale loc) {
  260.     /* try the cache first */
  261.     String[] dateTimePatterns = (String[]) cachedLocaleData.get(loc);
  262.     if (dateTimePatterns == null) { /* cache miss */
  263.         ResourceBundle r = ResourceBundle.getBundle
  264.         ("java.text.resources.LocaleElements", loc);
  265.         dateTimePatterns = r.getStringArray("DateTimePatterns");
  266.         /* update cache */
  267.         cachedLocaleData.put(loc, dateTimePatterns);
  268.     }
  269.  
  270.         formatData = new DateFormatSymbols(loc);
  271.         if ((timeStyle >= 0) && (dateStyle >= 0)) {
  272.             Object[] dateTimeArgs = {dateTimePatterns[timeStyle],
  273.                                      dateTimePatterns[dateStyle]};
  274.             pattern = MessageFormat.format(dateTimePatterns[8], dateTimeArgs);
  275.         }
  276.         else if (timeStyle >= 0)
  277.             pattern = dateTimePatterns[timeStyle];
  278.         else if (dateStyle >= 0)
  279.             pattern = dateTimePatterns[dateStyle];
  280.         else
  281.             throw new IllegalArgumentException("No date or time style specified");
  282.  
  283.         initialize(loc);
  284.     }
  285.  
  286.     /* Initialize calendar and numberFormat fields */
  287.     private void initialize(Locale loc) {
  288.         // The format object must be constructed using the symbols for this zone.
  289.         // However, the calendar should use the current default TimeZone.
  290.         // If this is not contained in the locale zone strings, then the zone
  291.         // will be formatted using generic GMT+/-H:MM nomenclature.
  292.         calendar = Calendar.getInstance(TimeZone.getDefault(), loc);
  293.         numberFormat = NumberFormat.getInstance(loc);
  294.         numberFormat.setGroupingUsed(false);
  295.         if (numberFormat instanceof DecimalFormat)
  296.             ((DecimalFormat)numberFormat).setDecimalSeparatorAlwaysShown(false);
  297.         numberFormat.setParseIntegerOnly(true); /* So that dd.mm.yy can be parsed */
  298.         numberFormat.setMinimumFractionDigits(0); // To prevent "Jan 1.00, 1997.00"
  299.  
  300.         initializeDefaultCentury();
  301.     }
  302.  
  303.     /* Initialize the fields we use to disambiguate ambiguous years. Separate
  304.      * so we can call it from readObject().
  305.      */
  306.     private void initializeDefaultCentury() {
  307.         calendar.setTime( new Date() );
  308.         calendar.add( Calendar.YEAR, -80 );
  309.         parseAmbiguousDatesAsAfter(calendar.getTime());
  310.     }
  311.  
  312.     /* Define one-century window into which to disambiguate dates using
  313.      * two-digit years. Make public in JDK 1.2.
  314.      */
  315.     private void parseAmbiguousDatesAsAfter(Date startDate) {
  316.         defaultCenturyStart = startDate;
  317.         calendar.setTime(startDate);
  318.         defaultCenturyStartYear = calendar.get(Calendar.YEAR);
  319.     }
  320.  
  321.     /**
  322.      * Overrides DateFormat
  323.      * <p>Formats a date or time, which is the standard millis
  324.      * since January 1, 1970, 00:00:00 GMT.
  325.      * <p>Example: using the US locale:
  326.      * "yyyy.MM.dd e 'at' HH:mm:ss zzz" ->> 1996.07.10 AD at 15:08:56 PDT
  327.      * @param date the date-time value to be formatted into a date-time string.
  328.      * @param toAppendTo where the new date-time text is to be appended.
  329.      * @param pos the formatting position. On input: an alignment field,
  330.      * if desired. On output: the offsets of the alignment field.
  331.      * @return the formatted date-time string.
  332.      * @see java.util.DateFormat
  333.      */
  334.     public StringBuffer format(Date date, StringBuffer toAppendTo,
  335.                                FieldPosition pos)
  336.     {
  337.         // Initialize
  338.         pos.beginIndex = pos.endIndex = 0;
  339.  
  340.         // Convert input date to time field list
  341.         calendar.setTime(date);
  342.  
  343.         boolean inQuote = false; // inQuote set true when hits 1st single quote
  344.         char prevCh = 0;
  345.         int count = 0;  // number of time pattern characters repeated
  346.         int interQuoteCount = 1; // Number of characters between quotes
  347.         for (int i=0; i<pattern.length(); ++i)
  348.         {
  349.             char ch = pattern.charAt(i);
  350.             if (inQuote)
  351.             {
  352.                 if (ch == '\'')
  353.                 {
  354.                     // ends with 2nd single quote
  355.                     inQuote = false;
  356.                     if (count == 0)
  357.                         toAppendTo.append(ch);  // two consecutive quotes outside a quote: ''
  358.                     else count = 0;
  359.                     interQuoteCount = 0;
  360.                 }
  361.                 else
  362.                 {
  363.                     toAppendTo.append(ch);
  364.                     count++;
  365.                 }
  366.             }
  367.             else // !inQuote
  368.             {
  369.                 if (ch == '\'')
  370.                 {
  371.                     inQuote = true;
  372.                     if (count > 0) // handle cases like: yyyy'....
  373.                     {
  374.                         toAppendTo.append(subFormat(prevCh, count,
  375.                                                     toAppendTo.length(),
  376.                                                     pos));
  377.                         count = 0;
  378.                         prevCh = 0;
  379.                     }
  380.  
  381.                     // We count characters between quotes so we can recognize
  382.                     // two single quotes inside a quote.  Example: 'o''clock'.
  383.                     if (interQuoteCount == 0)
  384.                     {
  385.                         toAppendTo.append(ch);
  386.                         count = 1; // Make it look like we never left.
  387.                     }
  388.                 }
  389.                 else if (ch >= 'a' && ch <= 'z' || ch >= 'A' && ch <= 'Z')
  390.                 {
  391.                     // ch is a date-time pattern
  392.                     if (ch != prevCh && count > 0) //handle cases: eg, yyyyMMdd
  393.                     {
  394.                         toAppendTo.append(subFormat(prevCh, count,
  395.                                                     toAppendTo.length(),
  396.                                                     pos));
  397.                         prevCh = ch;
  398.                         count = 1;
  399.                     }
  400.                     else
  401.                     {
  402.                         if (ch != prevCh)
  403.                             prevCh = ch;
  404.                         count++;
  405.                     }
  406.                 }
  407.                 else if (count > 0) // handle cases like: MM-dd-yy or HH:mm:ss
  408.                 {
  409.                     toAppendTo.append(subFormat(prevCh, count,
  410.                                                 toAppendTo.length(),
  411.                                                 pos));
  412.                     toAppendTo.append(ch);
  413.                     prevCh = 0;
  414.                     count = 0;
  415.                 }
  416.                 else // any other unquoted characters
  417.                     toAppendTo.append(ch);
  418.  
  419.                 ++interQuoteCount;
  420.             }
  421.         }
  422.         // Format the last item in the pattern
  423.         if (count > 0)
  424.         {
  425.             toAppendTo.append(subFormat(prevCh, count,
  426.                                         toAppendTo.length(), pos));
  427.         }
  428.         return toAppendTo;
  429.     }
  430.  
  431.     // Map index into pattern character string to Calendar field number
  432.     private static final int[] PATTERN_INDEX_TO_CALENDAR_FIELD =
  433.     {
  434.         Calendar.ERA, Calendar.YEAR, Calendar.MONTH, Calendar.DATE,
  435.         Calendar.HOUR_OF_DAY, Calendar.HOUR_OF_DAY, Calendar.MINUTE,
  436.         Calendar.SECOND, Calendar.MILLISECOND, Calendar.DAY_OF_WEEK,
  437.         Calendar.DAY_OF_YEAR, Calendar.DAY_OF_WEEK_IN_MONTH,
  438.         Calendar.WEEK_OF_YEAR, Calendar.WEEK_OF_MONTH,
  439.         Calendar.AM_PM, Calendar.HOUR, Calendar.HOUR, Calendar.ZONE_OFFSET
  440.     };
  441.  
  442.     // Map index into pattern character string to DateFormat field number
  443.     private static final int[] PATTERN_INDEX_TO_DATE_FORMAT_FIELD = {
  444.         DateFormat.ERA_FIELD, DateFormat.YEAR_FIELD, DateFormat.MONTH_FIELD,
  445.         DateFormat.DATE_FIELD, DateFormat.HOUR_OF_DAY1_FIELD,
  446.         DateFormat.HOUR_OF_DAY0_FIELD, DateFormat.MINUTE_FIELD,
  447.         DateFormat.SECOND_FIELD, DateFormat.MILLISECOND_FIELD,
  448.         DateFormat.DAY_OF_WEEK_FIELD, DateFormat.DAY_OF_YEAR_FIELD,
  449.         DateFormat.DAY_OF_WEEK_IN_MONTH_FIELD, DateFormat.WEEK_OF_YEAR_FIELD,
  450.         DateFormat.WEEK_OF_MONTH_FIELD, DateFormat.AM_PM_FIELD,
  451.         DateFormat.HOUR1_FIELD, DateFormat.HOUR0_FIELD,
  452.         DateFormat.TIMEZONE_FIELD,
  453.     };
  454.  
  455.     // Private member function that does the real date/time formatting.
  456.     private String subFormat(char ch, int count, int beginOffset,
  457.                              FieldPosition pos)
  458.          throws IllegalArgumentException
  459.     {
  460.         int     patternCharIndex = -1;
  461.         int     maxIntCount = 10;
  462.         String  current = "";
  463.  
  464.         if ((patternCharIndex=formatData.patternChars.indexOf(ch)) == -1)
  465.             throw new IllegalArgumentException("Illegal pattern character " +
  466.                                                "'" + ch + "'");
  467.  
  468.         int field = PATTERN_INDEX_TO_CALENDAR_FIELD[patternCharIndex];
  469.         int value = calendar.get(field);
  470.  
  471.         switch (patternCharIndex) {
  472.         case 0: // 'G' - ERA
  473.             current = formatData.eras[value];
  474.             break;
  475.         case 1: // 'y' - YEAR
  476.             if (count >= 4)
  477.                 //                current = zeroPaddingNumber(value, 4, count);
  478.                 current = zeroPaddingNumber(value, 4, maxIntCount);
  479.             else // count < 4
  480.                 current = zeroPaddingNumber(value, 2, 2); // clip 1996 to 96
  481.             break;
  482.         case 2: // 'M' - MONTH
  483.             if (count >= 4)
  484.                 current = formatData.months[value];
  485.             else if (count == 3)
  486.                 current = formatData.shortMonths[value];
  487.             else
  488.                 current = zeroPaddingNumber(value+1, count, maxIntCount);
  489.             break;
  490.         case 4: // 'k' - HOUR_OF_DAY: 1-based.  eg, 23:59 + 1 hour =>> 24:59
  491.             if (value == 0)
  492.                 current = zeroPaddingNumber(
  493.                                             calendar.getMaximum(Calendar.HOUR_OF_DAY)+1,
  494.                                             count, maxIntCount);
  495.             else
  496.                 current = zeroPaddingNumber(value, count, maxIntCount);
  497.             break;
  498.         case 8: // 'S' - MILLISECOND
  499.             if (count > 3)
  500.                 count = 3;
  501.             else if (count == 2)
  502.                 value = value / 10;
  503.             else if (count == 1)
  504.                 value = value / 100;
  505.             current = zeroPaddingNumber(value, count, maxIntCount);
  506.             break;
  507.         case 9: // 'E' - DAY_OF_WEEK
  508.             if (count >= 4)
  509.                 current = formatData.weekdays[value];
  510.             else // count < 4, use abbreviated form if exists
  511.                 current = formatData.shortWeekdays[value];
  512.             break;
  513.         case 14:    // 'a' - AM_PM
  514.             current = formatData.ampms[value];
  515.             break;
  516.         case 15: // 'h' - HOUR:1-based.  eg, 11PM + 1 hour =>> 12 AM
  517.             if (value == 0)
  518.                 current = zeroPaddingNumber(
  519.                                             calendar.getLeastMaximum(Calendar.HOUR)+1,
  520.                                             count, maxIntCount);
  521.             else
  522.                 current = zeroPaddingNumber(value, count, maxIntCount);
  523.             break;
  524.         case 17: // 'z' - ZONE_OFFSET
  525.             int zoneIndex
  526.                 = formatData.getZoneIndex (calendar.getTimeZone().getID());
  527.             if (zoneIndex == -1)
  528.             {
  529.                 // For time zones that have no names, use strings
  530.                 // GMT+hours:minutes and GMT-hours:minutes.
  531.                 // For instance, France time zone uses GMT+01:00.
  532.                 StringBuffer zoneString = new StringBuffer();
  533.  
  534.                 value = calendar.get(Calendar.ZONE_OFFSET) +
  535.                     calendar.get(Calendar.DST_OFFSET);
  536.  
  537.                 if (value < 0)
  538.                 {
  539.                     zoneString.append(GMT_MINUS);
  540.                     value = -value; // suppress the '-' sign for text display.
  541.                 }
  542.                 else
  543.                     zoneString.append(GMT_PLUS);
  544.                 zoneString.append(
  545.                                   zeroPaddingNumber((int)(value/millisPerHour), 2, 2));
  546.                 zoneString.append(':');
  547.                 zoneString.append(
  548.                                   zeroPaddingNumber(
  549.                                                     (int)((value%millisPerHour)/millisPerMinute), 2, 2));
  550.                 current = zoneString.toString();
  551.             }
  552.             else if (calendar.get(Calendar.DST_OFFSET) != 0)
  553.             {
  554.                 if (count >= 4)
  555.                     current = formatData.zoneStrings[zoneIndex][3];
  556.                 else
  557.                     // count < 4, use abbreviated form if exists
  558.                     current = formatData.zoneStrings[zoneIndex][4];
  559.             }
  560.             else
  561.             {
  562.                 if (count >= 4)
  563.                     current = formatData.zoneStrings[zoneIndex][1];
  564.                 else
  565.                     current = formatData.zoneStrings[zoneIndex][2];
  566.             }
  567.             break;
  568.         default:
  569.             // case 3: // 'd' - DATE
  570.             // case 5: // 'H' - HOUR_OF_DAY:0-based.  eg, 23:59 + 1 hour =>> 00:59
  571.             // case 6: // 'm' - MINUTE
  572.             // case 7: // 's' - SECOND
  573.             // case 10: // 'D' - DAY_OF_YEAR
  574.             // case 11: // 'F' - DAY_OF_WEEK_IN_MONTH
  575.             // case 12: // 'w' - WEEK_OF_YEAR
  576.             // case 13: // 'W' - WEEK_OF_MONTH
  577.             // case 16: // 'K' - HOUR: 0-based.  eg, 11PM + 1 hour =>> 0 AM
  578.             current = zeroPaddingNumber(value, count, maxIntCount);
  579.             break;
  580.         } // switch (patternCharIndex)
  581.  
  582.         if (pos.field == PATTERN_INDEX_TO_DATE_FORMAT_FIELD[patternCharIndex]) {
  583.             // set for the first occurence only.
  584.             if (pos.beginIndex == 0 && pos.endIndex == 0) {
  585.                 pos.beginIndex = beginOffset;
  586.                 pos.endIndex = beginOffset + current.length();
  587.             }
  588.         }
  589.  
  590.         return current;
  591.     }
  592.  
  593.  
  594.     // Pad the shorter numbers up to maxCount digits.
  595.     private String zeroPaddingNumber(long value, int minDigits, int maxDigits)
  596.     {
  597.         numberFormat.setMinimumIntegerDigits(minDigits);
  598.         numberFormat.setMaximumIntegerDigits(maxDigits);
  599.         return numberFormat.format(value);
  600.     }
  601.  
  602.  
  603.     /**
  604.      * Overrides DateFormat
  605.      * @see java.util.DateFormat
  606.      */
  607.     public Date parse(String text, ParsePosition pos)
  608.     {
  609.         int start = pos.index;
  610.         int oldStart = start;
  611.         boolean[] ambiguousYear = {false};
  612.  
  613.         calendar.clear(); // Clears all the time fields
  614.  
  615.         boolean inQuote = false; // inQuote set true when hits 1st single quote
  616.         char prevCh = 0;
  617.         int count = 0;
  618.         int interQuoteCount = 1; // Number of chars between quotes
  619.  
  620.         for (int i=0; i<pattern.length(); ++i)
  621.         {
  622.             char ch = pattern.charAt(i);
  623.  
  624.             if (inQuote)
  625.             {
  626.                 if (ch == '\'')
  627.                 {
  628.                     // ends with 2nd single quote
  629.                     inQuote = false;
  630.                     // two consecutive quotes outside a quote means we have
  631.                     // a quote literal we need to match.
  632.                     if (count == 0)
  633.                     {
  634.                         if (ch != text.charAt(start))
  635.                         {
  636.                             pos.index = oldStart;
  637.                             return null;
  638.                         }
  639.                         ++start;
  640.                     }
  641.                     count = 0;
  642.                     interQuoteCount = 0;
  643.                 }
  644.                 else
  645.                 {
  646.                     // pattern uses text following from 1st single quote.
  647.                     if (ch != text.charAt(start)) {
  648.                         // Check for cases like: 'at' in pattern vs "xt"
  649.                         // in time text, where 'a' doesn't match with 'x'.
  650.                         // If fail to match, return null.
  651.                         pos.index = oldStart; // left unchanged
  652.                         return null;
  653.                     }
  654.                     ++count;
  655.                     ++start;
  656.                 }
  657.             }
  658.             else    // !inQuote
  659.             {
  660.                 if (ch == '\'')
  661.                 {
  662.                     inQuote = true;
  663.                     if (count > 0) // handle cases like: e'at'
  664.                     {
  665.                         start=subParse(text, start, prevCh, count,
  666.                                        false, ambiguousYear);
  667.                         if ( start<0 ) {
  668.                             pos.index = oldStart;
  669.                             return null;
  670.                         }
  671.                         count = 0;
  672.                     }
  673.  
  674.                     if (interQuoteCount == 0)
  675.                     {
  676.                         // This indicates two consecutive quotes inside a quote,
  677.                         // for example, 'o''clock'.  We need to parse this as
  678.                         // representing a single quote within the quote.
  679.                         if (ch != text.charAt(start))
  680.                         {
  681.                             pos.index = oldStart;
  682.                             return null;
  683.                         }
  684.                         ++start;
  685.                         count = 1; // Make it look like we never left
  686.                     }
  687.                 }
  688.                 else if (ch >= 'a' && ch <= 'z' || ch >= 'A' && ch <= 'Z')
  689.                 {
  690.                     // ch is a date-time pattern
  691.                     if (ch != prevCh && count > 0) // e.g., yyyymmdd
  692.                     {
  693.                         // This is the only case where we pass in 'true' for
  694.                         // obeyCount.  That's because the next field directly
  695.                         // abuts this one, so we have to use the count to know when
  696.                         // to stop parsing. [LIU]
  697.                         start = subParse(text, start, prevCh, count, true,
  698.                                          ambiguousYear);
  699.                         if (start < 0) {
  700.                             pos.index = oldStart;
  701.                             return null;
  702.                         }
  703.                         prevCh = ch;
  704.                         count = 1;
  705.                     }
  706.                     else
  707.                     {
  708.                         if (ch != prevCh)
  709.                             prevCh = ch;
  710.                         count++;
  711.                     }
  712.                 }
  713.                 else if (count > 0)
  714.                 {
  715.                     // handle cases like: MM-dd-yy, HH:mm:ss, or yyyy MM dd,
  716.                     // where ch = '-', ':', or ' ', repectively.
  717.                     start=subParse(text, start, prevCh, count,
  718.                                    false, ambiguousYear);
  719.                     if ( start < 0 ) {
  720.                         pos.index = oldStart;
  721.                         return null;
  722.                     }
  723.                     if (start >= text.length() || ch != text.charAt(start)) {
  724.                         // handle cases like: 'MMMM dd' in pattern vs. "janx20"
  725.                         // in time text, where ' ' doesn't match with 'x'.
  726.                         pos.index = oldStart;
  727.                         return null;
  728.                     }
  729.                     start++;
  730.                     count = 0;
  731.                     prevCh = 0;
  732.                 }
  733.                 else // any other unquoted characters
  734.                 {
  735.                     if (ch != text.charAt(start)) {
  736.                         // handle cases like: 'MMMM   dd' in pattern vs.
  737.                         // "jan,,,20" in time text, where "   " doesn't
  738.                         // match with ",,,".
  739.                         pos.index = oldStart;
  740.                         return null;
  741.                     }
  742.                     start++;
  743.                 }
  744.  
  745.                 ++interQuoteCount;
  746.             }
  747.         }
  748.         // Parse the last item in the pattern
  749.         if (count > 0)
  750.         {
  751.             start=subParse(text, start, prevCh, count,
  752.                            false, ambiguousYear);
  753.             if ( start < 0 ) {
  754.                 pos.index = oldStart;
  755.                 return null;
  756.             }
  757.         }
  758.  
  759.         // At this point the fields of Calendar have been set.  Calendar
  760.         // will fill in default values for missing fields when the time
  761.         // is computed.
  762.  
  763.         pos.index = start;
  764.  
  765.         // This part is a problem:  When we call parsedDate.after, we compute the time.
  766.         // Take the date April 3 2004 at 2:30 am.  When this is first set up, the year
  767.         // will be wrong if we're parsing a 2-digit year pattern.  It will be 1904.
  768.         // April 3 1904 is a Sunday (unlike 2004) so it is the DST onset day.  2:30 am
  769.         // is therefore an "impossible" time, since the time goes from 1:59 to 3:00 am
  770.         // on that day.  It is therefore parsed out to fields as 3:30 am.  Then we
  771.         // add 100 years, and get April 3 2004 at 3:30 am.  Note that April 3 2004 is
  772.         // a Saturday, so it can have a 2:30 am -- and it should. [LIU]
  773.         /*
  774.         Date parsedDate = calendar.getTime();
  775.         if( ambiguousYear[0] && !parsedDate.after(defaultCenturyStart) ) {
  776.             calendar.add(Calendar.YEAR, 100);
  777.             parsedDate = calendar.getTime();
  778.         }
  779.         */
  780.         // Because of the above condition, save off the fields in case we need to readjust.
  781.         // The procedure we use here is not particularly efficient, but there is no other
  782.         // way to do this given the API restrictions present in Calendar.  We minimize
  783.         // inefficiency by only performing this computation when it might apply, that is,
  784.         // when the two-digit year is equal to the start year, and thus might fall at the
  785.         // front or the back of the default century.  This only works because we adjust
  786.         // the year correctly to start with in other cases -- see subParse().
  787.         Date parsedDate;
  788.         try {
  789.             if (ambiguousYear[0]) // If this is true then the two-digit year == the default start year
  790.             {
  791.                 // We need a copy of the fields, and we need to avoid triggering a call to
  792.                 // complete(), which will recalculate the fields.  Since we can't access
  793.                 // the fields[] array in Calendar, we clone the entire object.  This will
  794.                 // stop working if Calendar.clone() is ever rewritten to call complete().
  795.                 Calendar savedCalendar = (Calendar)calendar.clone();
  796.                 parsedDate = calendar.getTime();
  797.                 if (parsedDate.before(defaultCenturyStart))
  798.                 {
  799.                     // We can't use add here because that does a complete() first.
  800.                     savedCalendar.set(Calendar.YEAR, defaultCenturyStartYear + 100);
  801.                     parsedDate = savedCalendar.getTime();
  802.                 }
  803.             }
  804.             else parsedDate = calendar.getTime();
  805.         }
  806.         // An IllegalArgumentException will be thrown by Calendar.getTime()
  807.         // if any fields are out of range, e.g., MONTH == 17.
  808.         catch (IllegalArgumentException e) {
  809.             pos.index = oldStart;
  810.             return null;
  811.         }
  812.  
  813.         return parsedDate;
  814.     }
  815.  
  816.     /**
  817.      * Private code-size reduction function used by subParse.
  818.      * @param text the time text being parsed.
  819.      * @param start where to start parsing.
  820.      * @param field the date field being parsed.
  821.      * @param data the string array to parsed.
  822.      * @return the new start position if matching succeeded; a negative number
  823.      * indicating matching failure, otherwise.
  824.      */
  825.     private int matchString(String text, int start, int field, String[] data)
  826.     {
  827.         int i = 0;
  828.         int count = data.length;
  829.  
  830.         if (field == Calendar.DAY_OF_WEEK) i = 1;
  831.  
  832.         // There may be multiple strings in the data[] array which begin with
  833.         // the same prefix (e.g., Cerven and Cervenec (June and July) in Czech).
  834.         // We keep track of the longest match, and return that.  Note that this
  835.         // unfortunately requires us to test all array elements.
  836.         int bestMatchLength = 0, bestMatch = -1;
  837.         for (; i<count; ++i)
  838.         {
  839.             int length = data[i].length();
  840.             // Always compare if we have no match yet; otherwise only compare
  841.             // against potentially better matches (longer strings).
  842.             if (length > bestMatchLength &&
  843.                 text.regionMatches(true, start, data[i], 0, length))
  844.             {
  845.                 bestMatch = i;
  846.                 bestMatchLength = length;
  847.             }
  848.         }
  849.         if (bestMatch >= 0)
  850.         {
  851.             calendar.set(field, bestMatch);
  852.             return start + bestMatchLength;
  853.         }
  854.         return -start;
  855.     }
  856.  
  857.     /**
  858.      * Private member function that converts the parsed date strings into
  859.      * timeFields. Returns -start (for ParsePosition) if failed.
  860.      * @param text the time text to be parsed.
  861.      * @param start where to start parsing.
  862.      * @param ch the pattern character for the date field text to be parsed.
  863.      * @param count the count of a pattern character.
  864.      * @param obeyCount if true, then the next field directly abuts this one,
  865.      * and we should use the count to know when to stop parsing.
  866.      * @param ambiguousYear return parameter; upon return, if ambiguousYear[0]
  867.      * is true, then a two-digit year was parsed and may need to be readjusted.
  868.      * @return the new start position if matching succeeded; a negative number
  869.      * indicating matching failure, otherwise.
  870.      */
  871.     private int subParse(String text, int start, char ch, int count,
  872.                          boolean obeyCount, boolean[] ambiguousYear)
  873.     {
  874.         Number number;
  875.         int value = 0;
  876.         int i;
  877.         ParsePosition pos = new ParsePosition(0);
  878.         int patternCharIndex = -1;
  879.  
  880.         if ((patternCharIndex=formatData.patternChars.indexOf(ch)) == -1)
  881.             return -start;
  882.  
  883.         pos.index = start;
  884.  
  885.         int field = PATTERN_INDEX_TO_CALENDAR_FIELD[patternCharIndex];
  886.  
  887.         // If there are any spaces here, skip over them.  If we hit the end
  888.         // of the string, then fail.
  889.         for (;;) {
  890.             if (pos.index >= text.length()) return -start;
  891.             char c = text.charAt(pos.index);
  892.             if (c != ' ' && c != '\t') break;
  893.             ++pos.index;
  894.         }
  895.  
  896.         // We handle a few special cases here where we need to parse
  897.         // a number value.  We handle further, more generic cases below.  We need
  898.         // to handle some of them here because some fields require extra processing on
  899.         // the parsed value.
  900.         if (patternCharIndex == 4 /*HOUR_OF_DAY1_FIELD*/ ||
  901.             patternCharIndex == 15 /*HOUR1_FIELD*/ ||
  902.             (patternCharIndex == 2 /*MONTH_FIELD*/ && count <= 2) ||
  903.             patternCharIndex == 1 /*YEAR*/)
  904.         {
  905.             // It would be good to unify this with the obeyCount logic below,
  906.             // but that's going to be difficult.
  907.             if (obeyCount)
  908.             {
  909.                 if ((start+count) > text.length()) return -start;
  910.                 number = numberFormat.parse(text.substring(0, start+count), pos);
  911.             }
  912.             else number = numberFormat.parse(text, pos);
  913.             if (number == null) return -start;
  914.             value = number.intValue();
  915.         }
  916.  
  917.         switch (patternCharIndex)
  918.         {
  919.         case 0: // 'G' - ERA
  920.             return matchString(text, start, Calendar.ERA, formatData.eras);
  921.         case 1: // 'y' - YEAR
  922.             // If there are 4 or more YEAR pattern characters, this indicates
  923.             // that the year value is to be treated literally, without any
  924.             // two-digit year adjustments (e.g., from "01" to 2001).  Otherwise
  925.             // we made adjustments to place the 2-digit year in the proper
  926.             // century -- unless the given year itself is more than two characters.
  927.             if (count <= 2 && (pos.index - start) <= 2)
  928.             {
  929.                 // Assume for example that the defaultCenturyStart is 6/18/1903.
  930.                 // This means that two-digit years will be forced into the range
  931.                 // 6/18/1903 to 6/17/2003.  As a result, years 00, 01, and 02
  932.                 // correspond to 2000, 2001, and 2002.  Years 04, 05, etc. correspond
  933.                 // to 1904, 1905, etc.  If the year is 03, then it is 2003 if the
  934.                 // other fields specify a date before 6/18, or 1903 if they specify a
  935.                 // date afterwards.  As a result, 03 is an ambiguous year.  All other
  936.                 // two-digit years are unambiguous.
  937.                 int ambiguousTwoDigitYear = defaultCenturyStartYear % 100;
  938.                 ambiguousYear[0] = value == ambiguousTwoDigitYear;
  939.                 value += (defaultCenturyStartYear/100)*100 +
  940.                     (value < ambiguousTwoDigitYear ? 100 : 0);
  941.             }
  942.             calendar.set(Calendar.YEAR, value);
  943.             return pos.index;
  944.         case 2: // 'M' - MONTH
  945.             if (count <= 2) // i.e., M or MM.
  946.             {
  947.                 // Don't want to parse the month if it is a string
  948.                 // while pattern uses numeric style: M or MM.
  949.                 // [We computed 'value' above.]
  950.                 calendar.set(Calendar.MONTH, value - 1);
  951.                 return pos.index;
  952.             }
  953.             else
  954.             {
  955.                 // count >= 3 // i.e., MMM or MMMM
  956.                 // Want to be able to parse both short and long forms.
  957.                 // Try count == 4 first:
  958.                 int newStart = 0;
  959.                 if ((newStart=matchString(text, start, Calendar.MONTH,
  960.                                           formatData.months)) > 0)
  961.                     return newStart;
  962.                 else // count == 4 failed, now try count == 3
  963.                     return matchString(text, start, Calendar.MONTH,
  964.                                        formatData.shortMonths);
  965.             }
  966.         case 4: // 'k' - HOUR_OF_DAY: 1-based.  eg, 23:59 + 1 hour =>> 24:59
  967.             // [We computed 'value' above.]
  968.             if (value == calendar.getMaximum(Calendar.HOUR_OF_DAY)+1) value = 0;
  969.             calendar.set(Calendar.HOUR_OF_DAY, value);
  970.             return pos.index;
  971.         case 9: { // 'E' - DAY_OF_WEEK
  972.             // Want to be able to parse both short and long forms.
  973.             // Try count == 4 (DDDD) first:
  974.             int newStart = 0;
  975.             if ((newStart=matchString(text, start, Calendar.DAY_OF_WEEK,
  976.                                       formatData.weekdays)) > 0)
  977.                 return newStart;
  978.             else // DDDD failed, now try DDD
  979.                 return matchString(text, start, Calendar.DAY_OF_WEEK,
  980.                                    formatData.shortWeekdays);
  981.         }
  982.         case 14:    // 'a' - AM_PM
  983.             return matchString(text, start, Calendar.AM_PM, formatData.ampms);
  984.         case 15: // 'h' - HOUR:1-based.  eg, 11PM + 1 hour =>> 12 AM
  985.             // [We computed 'value' above.]
  986.             if (value == calendar.getLeastMaximum(Calendar.HOUR)+1) value = 0;
  987.             calendar.set(Calendar.HOUR, value);
  988.             return pos.index;
  989.         case 17: // 'z' - ZONE_OFFSET
  990.             // First try to parse generic forms such as GMT-07:00. Do this first
  991.             // in case localized DateFormatZoneData contains the string "GMT"
  992.             // for a zone; in that case, we don't want to match the first three
  993.             // characters of GMT+/-HH:MM etc.
  994.             {
  995.                 int sign = 0;
  996.                 int offset;
  997.  
  998.                 // For time zones that have no known names, look for strings
  999.                 // of the form:
  1000.                 //    GMT[+-]hours:minutes or
  1001.                 //    GMT[+-]hhmm or
  1002.                 //    GMT.
  1003.                 if (text.regionMatches(true,start, GMT, 0, GMT.length()))
  1004.                 {
  1005.                     calendar.set(Calendar.DST_OFFSET, 0);
  1006.  
  1007.                     pos.index = start + GMT.length();
  1008.                     
  1009.                     if (pos.index == text.length()) {
  1010.                           calendar.set(Calendar.ZONE_OFFSET, 0 );
  1011.                           return pos.index;
  1012.                     }
  1013.                     else if( text.charAt(pos.index) == '+' )
  1014.                         sign = 1; 
  1015.                     else if( text.charAt(pos.index) == '-' )
  1016.                         sign = -1;
  1017.                     else {
  1018.                         calendar.set(Calendar.ZONE_OFFSET, 0 );
  1019.                         return pos.index;
  1020.                     }
  1021.  
  1022.                     // Look for hours:minutes or hhmm.
  1023.                     pos.index++;
  1024.                     Number tzNumber = numberFormat.parse(text, pos);
  1025.                     if( tzNumber == null ) {
  1026.                         return -start;
  1027.                     }
  1028.                     if( text.charAt(pos.index) == ':' ) {
  1029.                         // This is the hours:minutes case
  1030.                         offset = tzNumber.intValue() * 60;
  1031.                         pos.index++;
  1032.                         tzNumber = numberFormat.parse(text, pos);
  1033.                         if( tzNumber == null ) {
  1034.                             return -start;
  1035.                         }
  1036.                         offset += tzNumber.intValue();
  1037.                     }
  1038.                     else {
  1039.                         // This is the hhmm case.
  1040.                         offset = tzNumber.intValue();
  1041.                         if( offset < 24 )
  1042.                             offset *= 60;
  1043.                         else
  1044.                             offset = offset % 100 + offset / 100 * 60;
  1045.                     }
  1046.  
  1047.                     // Fall through for final processing below of 'offset' and 'sign'.
  1048.                 }
  1049.                 else {
  1050.                     // At this point, check for named time zones by looking through
  1051.                     // the locale data from the DateFormatZoneData strings.
  1052.                     // Want to be able to parse both short and long forms.
  1053.                     for (i=0; i<formatData.zoneStrings.length; i++)
  1054.                     {
  1055.                         // Checking long and short zones [1 & 2],
  1056.                         // and long and short daylight [3 & 4].
  1057.                         int j = 1;
  1058.                         for (; j <= 4; ++j)
  1059.                         {
  1060.                             if (text.regionMatches(true, start,
  1061.                                                    formatData.zoneStrings[i][j], 0,
  1062.                                                    formatData.zoneStrings[i][j].length()))
  1063.                                 break;
  1064.                         }
  1065.                         if (j <= 4)
  1066.                         {
  1067.                             TimeZone tz = TimeZone.getTimeZone(formatData.zoneStrings[i][0]);
  1068.                             calendar.set(Calendar.ZONE_OFFSET, tz.getRawOffset());
  1069.                             // Must call set() with something -- TODO -- Fix this to
  1070.                             // use the correct DST SAVINGS for the zone.
  1071.                             calendar.set(Calendar.DST_OFFSET, j >= 3 ? millisPerHour : 0);
  1072.                             return (start + formatData.zoneStrings[i][j].length());
  1073.                         }
  1074.                     }
  1075.  
  1076.                     // As a last resort, look for numeric timezones of the form
  1077.                     // [+-]hhmm as specified by RFC 822.  This code is actually
  1078.                     // a little more permissive than RFC 822.  It will try to do
  1079.                     // its best with numbers that aren't strictly 4 digits long.
  1080.                     DecimalFormat fmt = new DecimalFormat("+####;-####");
  1081.                     fmt.setParseIntegerOnly(true);
  1082.                     Number tzNumber = fmt.parse( text, pos );
  1083.                     if( tzNumber == null ) {
  1084.                         return -start;   // Wasn't actually a number.
  1085.                     }
  1086.                     offset = tzNumber.intValue();
  1087.                     sign = 1;
  1088.                     if( offset < 0 ) {
  1089.                         sign = -1;
  1090.                         offset = -offset;
  1091.                     }
  1092.                     if( offset < 24 )
  1093.                         offset = offset * 60;
  1094.                     else
  1095.                         offset = offset % 100 + offset / 100 * 60;
  1096.  
  1097.                     // Fall through for final processing below of 'offset' and 'sign'.
  1098.                 }
  1099.  
  1100.                 // Do the final processing for both of the above cases.  We only
  1101.                 // arrive here if the form GMT+/-... or an RFC 822 form was seen.
  1102.                 if (sign != 0)
  1103.                 {
  1104.                     offset *= millisPerMinute * sign;
  1105.  
  1106.                     if (calendar.getTimeZone().useDaylightTime())
  1107.                     {
  1108.                         calendar.set(Calendar.DST_OFFSET, millisPerHour);
  1109.                         offset -= millisPerHour;
  1110.                     }
  1111.                     calendar.set(Calendar.ZONE_OFFSET, offset);
  1112.  
  1113.                     return pos.index;
  1114.                 }
  1115.             }
  1116.  
  1117.             // All efforts to parse a zone failed.
  1118.             return -start;
  1119.  
  1120.         default:
  1121.             // case 3: // 'd' - DATE
  1122.             // case 5: // 'H' - HOUR_OF_DAY:0-based.  eg, 23:59 + 1 hour =>> 00:59
  1123.             // case 6: // 'm' - MINUTE
  1124.             // case 7: // 's' - SECOND
  1125.             // case 8: // 'S' - MILLISECOND
  1126.             // case 10: // 'D' - DAY_OF_YEAR
  1127.             // case 11: // 'F' - DAY_OF_WEEK_IN_MONTH
  1128.             // case 12: // 'w' - WEEK_OF_YEAR
  1129.             // case 13: // 'W' - WEEK_OF_MONTH
  1130.             // case 16: // 'K' - HOUR: 0-based.  eg, 11PM + 1 hour =>> 0 AM
  1131.  
  1132.             // Handle "generic" fields
  1133.             if (obeyCount)
  1134.             {
  1135.                 if ((start+count) > text.length()) return -start;
  1136.                 number = numberFormat.parse(text.substring(0, start+count), pos);
  1137.             }
  1138.             else number = numberFormat.parse(text, pos);
  1139.             if (number != null)
  1140.             {
  1141.                 calendar.set(field, number.intValue());
  1142.                 return pos.index;
  1143.             }
  1144.             return -start;
  1145.         }
  1146.     }
  1147.  
  1148.  
  1149.     /**
  1150.      * Translate a pattern, mapping each character in the from string to the
  1151.      * corresponding character in the to string.
  1152.      */
  1153.     private String translatePattern(String pattern, String from, String to) {
  1154.         StringBuffer result = new StringBuffer();
  1155.         boolean inQuote = false;
  1156.         for (int i = 0; i < pattern.length(); ++i) {
  1157.             char c = pattern.charAt(i);
  1158.             if (inQuote) {
  1159.                 if (c == '\'')
  1160.                     inQuote = false;
  1161.             }
  1162.             else {
  1163.                 if (c == '\'')
  1164.                     inQuote = true;
  1165.                 else if ((c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z')) {
  1166.                     int ci = from.indexOf(c);
  1167.                     if (ci == -1)
  1168.                         throw new IllegalArgumentException("Illegal pattern " +
  1169.                                                            " character '" +
  1170.                                                            c + "'");
  1171.                     c = to.charAt(ci);
  1172.                 }
  1173.             }
  1174.             result.append(c);
  1175.         }
  1176.         if (inQuote)
  1177.             throw new IllegalArgumentException("Unfinished quote in pattern");
  1178.         return result.toString();
  1179.     }
  1180.  
  1181.     /**
  1182.      * Return a pattern string describing this date format.
  1183.      */
  1184.     public String toPattern() {
  1185.         return pattern;
  1186.     }
  1187.  
  1188.     /**
  1189.      * Return a localized pattern string describing this date format.
  1190.      */
  1191.     public String toLocalizedPattern() {
  1192.         return translatePattern(pattern,
  1193.                                 formatData.patternChars,
  1194.                                 formatData.localPatternChars);
  1195.     }
  1196.  
  1197.     /**
  1198.      * Apply the given unlocalized pattern string to this date format.
  1199.      */
  1200.     public void applyPattern (String pattern)
  1201.     {
  1202.         this.pattern = pattern;
  1203.     }
  1204.  
  1205.     /**
  1206.      * Apply the given localized pattern string to this date format.
  1207.      */
  1208.     public void applyLocalizedPattern(String pattern) {
  1209.         this.pattern = translatePattern(pattern,
  1210.                                         formatData.localPatternChars,
  1211.                                         formatData.patternChars);
  1212.     }
  1213.  
  1214.     /**
  1215.      * Gets the date/time formatting data.
  1216.      * @return a copy of the date-time formatting data associated
  1217.      * with this date-time formatter.
  1218.      */
  1219.     public DateFormatSymbols getDateFormatSymbols()
  1220.     {
  1221.         return (DateFormatSymbols)formatData.clone();
  1222.     }
  1223.  
  1224.     /**
  1225.      * Allows you to set the date/time formatting data.
  1226.      * @param newFormatData the given date-time formatting data.
  1227.      */
  1228.     public void setDateFormatSymbols(DateFormatSymbols newFormatSymbols)
  1229.     {
  1230.         this.formatData = (DateFormatSymbols)newFormatSymbols.clone();
  1231.     }
  1232.  
  1233.     /**
  1234.      * Overrides Cloneable
  1235.      */
  1236.     public Object clone() {
  1237.         SimpleDateFormat other = (SimpleDateFormat) super.clone();
  1238.         other.formatData = (DateFormatSymbols) formatData.clone();
  1239.         return other;
  1240.     }
  1241.  
  1242.     /**
  1243.      * Override hashCode.
  1244.      * Generates the hash code for the SimpleDateFormat object
  1245.      */
  1246.     public int hashCode()
  1247.     {
  1248.         return pattern.hashCode();
  1249.         // just enough fields for a reasonable distribution
  1250.     }
  1251.  
  1252.     /**
  1253.      * Override equals.
  1254.      */
  1255.     public boolean equals(Object obj)
  1256.     {
  1257.         if (!super.equals(obj)) return false; // super does class check
  1258.         SimpleDateFormat that = (SimpleDateFormat) obj;
  1259.         return (pattern.equals(that.pattern)
  1260.                 && formatData.equals(that.formatData));
  1261.     }
  1262.  
  1263.     /**
  1264.      * Override readObject.
  1265.      */
  1266.     private void readObject(ObjectInputStream stream)
  1267.          throws IOException, ClassNotFoundException {
  1268.              stream.defaultReadObject();
  1269.              if (serialVersionOnStream < 1) {
  1270.                  // didn't have defaultCenturyStart field
  1271.                  initializeDefaultCentury();
  1272.              }
  1273.              else {
  1274.                  // fill in dependent transient field
  1275.                  parseAmbiguousDatesAsAfter(defaultCenturyStart);
  1276.              }
  1277.              serialVersionOnStream = currentSerialVersion;
  1278.     }
  1279. }
  1280.